# -*- coding: utf-8 -*-
# @Time    : 2024/6/25 10:56
# @Author  : yujiahao
# @File    : 09_pandas_statistical_function.py
# @description:pandas中的统计函数


'''Python Pandas统计函数

Pandas 的本质是统计学原理在计算机领域的一种应用实现，通过编程的方式达到分析、描述数据的目的。
而统计函数则是统计学中用于计算和分析数据的一种工具。在数据分析的过程中，使用统计函数有助于我们理解和分析数据。
本节将学习几个常见的统计函数，比如百分比函数、协方差函数、相关系数等。
'''
import pandas as pd
import numpy as np


# todo 1、百分比变化(pct_change)
def pandas_pct_change():
    '''Series 和 DatFrames 都可以使用 pct_change() 函数。该函数将每个元素与其前一个元素进行比较，并计算前后数值的百分比变化。
    默认情况下，pct_change() 对列进行操作，如果想要操作行，则需要传递参数 axis=1 参数。
    s = pd.Series([1, 2, 3, 4, 5, 4])

    print(df.pct_change())

    变化过程详细解释:

        第一个元素：1
        由于没有前一个元素进行比较，所以结果为 NaN。

        第二个元素：2
        变化率计算： (2 - 1) / 1 = 1.0 或者 100%
        百分比变化：1.000000

        第三个元素：3
        变化率计算： (3 - 2) / 2 = 0.5 或者 50%
        百分比变化：0.500000

        第四个元素：4
        变化率计算： (4 - 3) / 3 = 0.333333 或者 33.33%
        百分比变化：0.333333

        第五个元素：5
        变化率计算： (5 - 4) / 4 = 0.25 或者 25%
        百分比变化：0.250000

        第六个元素：4
        变化率计算： (4 - 5) / 5 = -0.2 或者 -20%
        百分比变化：-0.200000

        总结:
        pct_change() 函数计算的是每个元素相对于前一个元素的变化百分比。具体来说，对于 Series 中的第 i 个元素，百分比变化计算公式为：

        pct_change[i] = (Series[i] - Series[i-1]) / Series[i-1]

        上述示例中，我们逐个计算了每个元素的变化百分比，并将结果存储在新的 Series 中。第一个元素没有前一个元素进行比较，所以结果为 NaN。

    '''

    # Series结构
    s = pd.Series([1, 2, 3, 4, 5, 4])
    print(s)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    print(s.pct_change())

    # DataFrame
    df = pd.DataFrame(np.random.randn(5, 2))
    print(df.pct_change())


# todo 2、协方差
def pandas_cov():
    """
    协方差（Covariance）是统计学中用来衡量两个随机变量之间关系的度量。具体来说，协方差表示的是两个变量如何一起变化的程度。
    - 如果两个变量的协方差为正，则表示当一个变量增加时，另一个变量也倾向于增加。
    - 如果协方差为负，则表示当一个变量增加时，另一个变量倾向于减少。
    - 如果协方差接近于零，则表示两个变量之间没有明显的线性关系。

    协方差的公式：
        Cov(X, Y) = E[(X - E[X])(Y - E[Y])]

    其中：
    - E[X] 表示 X 的期望值（均值）
    - E[Y] 表示 Y 的期望值（均值）

    计算协方差的步骤：
    1. 计算每个变量的均值：
        - X̄ = (1/n) ∑ Xi
        - Ȳ = (1/n) ∑ Yi

    2. 计算每对数据的差值：
        - Xi - X̄
        - Yi - Ȳ

    3. 计算每对差值的乘积的平均值：
        - Cov(X, Y) = (1/n) ∑ (Xi - X̄)(Yi - Ȳ)

    示例：
    假设我们有两个变量 X 和 Y 的观测值如下：
        X = [1, 2, 3, 4, 5]
        Y = [2, 4, 6, 8, 10]

    1. 计算均值：
        - X̄ = (1+2+3+4+5)/5 = 3
        - Ȳ = (2+4+6+8+10)/5 = 6

    2. 计算差值：
        - Xi - X̄ = [-2, -1, 0, 1, 2]
        - Yi - Ȳ = [-4, -2, 0, 2, 4]

    3. 计算乘积并求平均值：
        - (Xi - X̄)(Yi - Ȳ) = [8, 2, 0, 2, 8]
        - Cov(X, Y) = (8 + 2 + 0 + 2 + 8)/5 = 4

    因此，变量 X 和 Y 的协方差为 4，表示这两个变量之间有正向的线性关系。



    Series 对象提供了一个cov方法用来计算 Series 对象之间的协方差。同时，该方法也会将缺失值(NAN )自动排除。

    """

    s1 = pd.Series(np.random.randn(10))
    s2 = pd.Series(np.random.randn(10))
    print(s1.cov(s2))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 当应用于 DataFrame 时，协方差（cov）将计算所有列之间的协方差。
    frame = pd.DataFrame(np.random.randn(10, 5), columns=['a', 'b', 'c', 'd', 'e'])
    # 计算a与b之间的协方差值
    print(frame['a'].cov(frame['b']))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 计算所有数列的协方差值
    print(frame.cov())


# todo 3、相关系数
def pandas_corr():
    """
    相关系数（Correlation Coefficient）是统计学中用来衡量两个变量之间线性关系强度和方向的度量。它的取值范围在 -1 和 1 之间：
    - 1 表示两个变量之间存在完全正线性关系。
    - -1 表示两个变量之间存在完全负线性关系。
    - 0 表示两个变量之间没有线性关系。

    最常用的相关系数是皮尔逊相关系数（Pearson Correlation Coefficient），它是协方差的标准化形式。

    皮尔逊相关系数的公式：
        r = Cov(X, Y) / (σ_X σ_Y)

    其中：
    - Cov(X, Y) 是 X 和 Y 的协方差
    - σ_X 是 X 的标准差
    - σ_Y 是 Y 的标准差

    计算皮尔逊相关系数的步骤：
    1. 计算每个变量的均值：
        - X̄ = (1/n) ∑ Xi
        - Ȳ = (1/n) ∑ Yi

    2. 计算每对数据的差值：
        - Xi - X̄
        - Yi - Ȳ

    3. 计算每对差值的乘积的平均值（协方差）：
        - Cov(X, Y) = (1/n) ∑ (Xi - X̄)(Yi - Ȳ)

    4. 计算每个变量的标准差：
        - σ_X = √((1/n) ∑ (Xi - X̄)^2)
        - σ_Y = √((1/n) ∑ (Yi - Ȳ)^2)

    5. 计算相关系数：
        - r = Cov(X, Y) / (σ_X σ_Y)

    示例：
    假设我们有两个变量 X 和 Y 的观测值如下：
        X = [1, 2, 3, 4, 5]
        Y = [2, 4, 6, 8, 10]

    1. 计算均值：
        - X̄ = (1+2+3+4+5)/5 = 3
        - Ȳ = (2+4+6+8+10)/5 = 6

    2. 计算差值：
        - Xi - X̄ = [-2, -1, 0, 1, 2]
        - Yi - Ȳ = [-4, -2, 0, 2, 4]

    3. 计算协方差：
        - Cov(X, Y) = (8 + 2 + 0 + 2 + 8)/5 = 4

    4. 计算标准差：
        - σ_X = √((1/5) ∑ (Xi - X̄)^2) = √(2) ≈ 1.414
        - σ_Y = √((1/5) ∑ (Yi - Ȳ)^2) = √(8) ≈ 2.828

    5. 计算相关系数：
        - r = 4 / (1.414 * 2.828) = 1

    因此，变量 X 和 Y 之间的相关系数为 1，表示它们之间存在完全正线性关系。

相关系数显示任意两个 Series 之间的线性关系。Pandas 提供了计算相关性的三种方法，分别是 pearson(default)、spearman() 和 kendall()。
注意：如果 DataFrame 存在非数值(NAN），该方法会自动将其删除。
    """

    # 示例数据
    X = np.array([1, 2, 3, 4, 5])
    Y = np.array([2, 4, 6, 8, 10])
    # 使用 numpy 计算相关系数
    corr_numpy = np.corrcoef(X, Y)[0, 1]
    print(f"使用 numpy 计算相关系数: {corr_numpy}")

    # 使用 pandas 计算相关系数
    df = pd.DataFrame({'X': X, 'Y': Y})
    corr_pandas = df['X'].corr(df['Y'])
    print(f"使用 pandas 计算相关系数: {corr_pandas}")


def pandas_rank():
    '''rank() 按照某种规则（升序或者降序）对序列中的元素值排名，该函数的返回值的也是一个序列，包含了原序列中每个元素值的名次。
    如果序列中包含两个相同的的元素值，那么会为其分配两者的平均排名'''

    # 返回5个随机值，然后使用rank对其排名
    s = pd.Series(np.random.randn(5), index=list('abcde'))
    s['d'] = s['b']
    print(s)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 这里就是如果两个数值相同，按照排序的顺序号相加除以2就是这两个数的平均排名(默认)，其他的排名不变是几就是几
    print(s.rank())

    '''
    1) method参数
        rank() 提供了 method 参数，可以针对相同数据，进行不同方式的排名。如下所示：
        average：默认值，如果数据相同则分配平均排名；
        min：给相同数据分配最低排名；
        max：给相同数据分配最大排名；
        first：对于相同数据，根据出现在数组中的顺序进行排名。
    2) aisx&ascening
        rank() 有一个ascening参数， 默认为 True 代表升序；如果为 False，则表示降序排名（将较大的数值分配给较小的排名）。
    
        rank() 默认按行方向排名（axis=0），也可以更改为 axis =1，按列排名。
    '''

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    a = pd.DataFrame(np.arange(12).reshape(3, 4), columns=list("abdc"))
    a = a.sort_index(axis=1, ascending=False)
    a.iloc[[1, 1], [1, 2]] = 6
    # 按行排名，将相同数值设置为所在行数值的最大排名
    print(a.rank(axis=1, method="max"))


# todo 5、窗口函数

    '''
    了能更好地处理数值型数据，Pandas 提供了几种窗口函数，比如移动函数（rolling）、扩展函数（expanding）和指数加权函数（ewm）。
    窗口函数应用场景非常多。举一个简单的例子：现在有 10 天的销售额，而您想每 3 天求一次销售总和，也就说第五天的销售额等于（第三天 + 第四天 + 第五天）的销售额之和，此时窗口函数就派上用场了。
    窗口是一种形象化的叫法，这些函数在执行操作时，就如同窗口一样在数据区间上移动。
    '''
def pandas_over():

    # todo 5.1、rolling()

    """
    rolling() 又称移动窗口函数，它可以与 mean、count、sum、median、std 等聚合函数一起使用。
    为了使用方便，Pandas 为移动函数定义了专门的方法聚合方法，比如 rolling_mean()、rolling_count()、rolling_sum() 等。
    其语法格式如下：

    rolling(window=n, min_periods=None, center=False)

    常用参数说明如下：

    参数名称    说明
    window      默认值为 1，表示窗口的大小，也就是观测值的数量。
    min_periods 表示窗口的最小观察值，默认与 window 的参数值相等。表示窗口有 多少个值就进行计算，而不会返回NaN
    center      是否把中间值做为窗口标准，默认值为 False。

    下面看一组示例：

    # 生成时间序列
    df = pd.DataFrame(np.random.randn(8, 4), index=pd.date_range('12/1/2020', periods=8), columns=['A', 'B', 'C', 'D'])
    print(df)

    # 每3个数求一次均值
    print(df.rolling(window=3).mean())
    """
    # 生成时间序列
    df = pd.DataFrame(np.random.randn(8, 4), index=pd.date_range('12/1/2020', periods=8), columns=['A', 'B', 'C', 'D'])
    print(df)

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 每3个数求求一次均值
    '''window=3表示是每一列中依次紧邻的每 3 个数求一次均值。当不满足 3 个数时，所求值均为 NaN 值，因此前两列的值为 NaN，直到第三行值才满足要求 window =3。求均值的公式如下所示：
        (index1+index2+index3)/3
    '''
    print(df.rolling(window=3).mean())

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # 可以跟聚合函数
    r = df.rolling(window=3, min_periods=1)
    # 可以多列进行多个函数计算，也可以分别指定计算print(r.aggregate({'A': np.sum,'B': np.mean}))
    print(r['A', 'B'].aggregate([np.sum, np.mean]))

    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')

    # todo 5.2、expanding()
    '''
    expanding() 又叫扩展窗口函数，扩展是指由序列的第一个元素开始，逐个向后计算元素的聚合值。计算累计值，如果不足min_periods就是NaN, (index0+index1+...+indexn)/min_periods（指定的值）
    '''
    # 下面示例，min_periods = n表示向后移动 n 个值计求一次平均值
    print(df.expanding(min_periods=3).mean())


    print('''\n-----------------------------------☆☆☆分割线☆☆☆----------------------------------------\n''')



    # todo 5.3、ewm()

    '''
    ewm（全称 Exponentially Weighted Moving）表示指数加权移动。
    ewn() 函数先会对序列元素做指数加权运算，其次计算加权后的均值。
    该函数通过指定 com、span 或者 halflife 参数来实现指数加权移动。
    
    指数加权移动平均（Exponentially Weighted Moving Average, EWMA）是一种通过对历史数据赋予指数衰减权重来计算平均值的方法。
    与简单移动平均不同，EWMA 对最近的数据点赋予更大的权重，对较早的数据点赋予较小的权重。
    这种加权方式使得 EWMA 对最新的数据变化更为敏感，适用于捕捉时间序列中的最新趋势。
    
    权重的计算：
    在 EWMA 中，权重是通过一个衰减因子（通常称为 α）来计算的。这个衰减因子决定了权重的衰减速度。
    常见的计算方式是通过指定一个窗口长度（span），然后通过以下公式计算 α：
    
        α = 2 / (N + 1)
    
    其中 N 是窗口长度。
    
    权重的分配：
    假设我们有一个时间序列数据 x1, x2, ..., xt，EWMA 的计算公式为：
    
        ẋt = α xt + (1 - α) ẋt-1
    
    其中：
    - ẋt 是第 t 个时间点的 EWMA 值。
    - xt 是第 t 个时间点的原始数据值。
    - α 是衰减因子。
    
    递推公式：
    通过递推公式，我们可以看到每一个新的 EWMA 值是由当前数据点和前一个 EWMA 值加权得到的。
    可以展开递推公式，得到：
    
        ẋt = α xt + α (1 - α) xt-1 + α (1 - α)^2 xt-2 + ...
    
    这表示 EWMA 是所有历史数据的加权和，其中权重是指数衰减的。
    
    示例：
    假设我们有以下数据：
    
        data = {
            'A': [1.069303, 1.367798, -0.252852, -0.006437, -0.787359, 0.778384, 1.758588, 2.158982],
        }
    
        index = pd.date_range('2020-12-01', periods=8)
        df = pd.DataFrame(data, index=index)
    
    计算 EWMA：
    
        ewm_mean = df['A'].ewm(span=3).mean()
    
    结果：
    
        2020-12-01    1.069303
        2020-12-02    1.235801
        2020-12-03    0.561917
        2020-12-04    0.364360
        2020-12-05   -0.133227
        2020-12-06    0.211738
        2020-12-07    0.790355
        2020-12-08    1.368133
        Freq: D, Name: A, dtype: float64
    
    计算过程解析：
    假设 α = 0.5，则计算过程如下：
    
    1. 2020-12-01:
        ẋ1 = x1 = 1.069303
    
    2. 2020-12-02:
        ẋ2 = α x2 + (1 - α) ẋ1 = 0.5 * 1.367798 + 0.5 * 1.069303 = 1.235801
    
    3. 2020-12-03:
        ẋ3 = α x3 + (1 - α) ẋ2 = 0.5 * (-0.252852) + 0.5 * 1.235801 = 0.561917
    
    4. 2020-12-04:
        ẋ4 = α x4 + (1 - α) ẋ3 = 0.5 * (-0.006437) + 0.5 * 0.561917 = 0.364360
    
    5. 2020-12-05:
        ẋ5 = α x5 + (1 - α) ẋ4 = 0.5 * (-0.787359) + 0.5 * 0.364360 = -0.133227
    
    6. 2020-12-06:
        ẋ6 = α x6 + (1 - α) ẋ5 = 0.5 * 0.778384 + 0.5 * (-0.133227) = 0.211738
    
    7. 2020-12-07:
        ẋ7 = α x7 + (1 - α) ẋ6 = 0.5 * 1.758588 + 0.5 * 0.211738 = 0.790355
    
    8. 2020-12-08:
        ẋ8 = α x8 + (1 - α) ẋ7 = 0.5 * 2.158982 + 0.5 * 0.790355 = 1.368133
    
    再看个例子：
            
            示例数据
        假设 df['A'] 的前几行数据如下：
        日期          A
        2020-12-01    0.5
        2020-12-02    -1.5
        2020-12-03    2.0
        2020-12-04    1.0
        ...           ...
        
        计算公式
        指数加权均值的计算公式为：
        EWM_t = (1 - α) * EWM_{t-1} + α * x_t
        
        其中：
        α = 1 / (1 + com)
        
        对于 com=0.5，我们有：
        α = 1 / (1 + 0.5) = 1 / 1.5 ≈ 0.6667
        
        计算步骤
        初始值：
        通常，初始值的 EWM 就是该点的值本身，即 EWM_1 = x_1。
        
        递归计算：
        使用公式 EWM_t = (1 - α) * EWM_{t-1} + α * x_t 递归计算每个时间点的 EWM。
        
        具体计算
        假设数据为：
        日期          A
        2020-12-01    0.5
        2020-12-02    -1.5
        2020-12-03    2.0
        2020-12-04    1.0
        
        我们逐步计算 EWM：
        初始值：
        EWM_{2020-12-01} = x_{2020-12-01} = 0.5
        
        第二个值：
        EWM_{2020-12-02} = (1 - 0.6667) * 0.5 + 0.6667 * (-1.5) = 0.3333 * 0.5 + 0.6667 * (-1.5) = 0.16665 - 1.00005 ≈ -0.8334
        
        第三个值：
        EWM_{2020-12-03} = (1 - 0.6667) * (-0.8334) + 0.6667 * 2.0 = 0.3333 * (-0.8334) + 0.6667 * 2.0 = -0.2778 + 1.3334 ≈ 1.0556
        
        第四个值：
        EWM_{2020-12-04} = (1 - 0.6667) * 1.0556 + 0.6667 * 1.0 = 0.3333 * 1.0556 + 0.6667 * 1.0 = 0.3519 + 0.6667 ≈ 1.0186
        
        依此类推，我们可以继续计算后续的 EWM 值。
        
        总结
        通过上述步骤，我们可以看到每个时间点的 EWM 是如何计算的。
        每个新的 EWM 值都基于前一个 EWM 值和当前的数据点，结合权重参数 α 进行加权平均。
        
        在实际应用中，Pandas 提供了便捷的方法来计算 EWM，因此我们不需要手动计算每个值。
        通过 df['A'].ewm(com=0.5).mean()，Pandas 会自动为我们完成这些计算。

    
    总结：
    通过这种加权方式，EWMA 对最近的数据点赋予更大的权重，从而更好地反映最新的数据趋势。
    权重的指数衰减使得较早的数据点对当前的影响逐渐减小。
    这种方法在时间序列分析中非常有用，特别是在需要快速响应最新数据变化的情况下。
    '''

    df = pd.DataFrame(np.random.randn(10, 4),
                      index=pd.date_range('12/1/2020', periods=10),
                      columns=['A', 'B', 'C', 'D'])
    # 设置com=0.5，先加权再求均值
    print(df.ewm(com=0.5).mean())
def main():
    # pandas_pct_change()
    # pandas_cov()
    # pandas_corr()
    # pandas_rank()
    pandas_over()

if __name__ == '__main__':
    main()
